// +build windows
package main

import (
	"fmt"
	"golang.org/x/sys/windows"
	"path/filepath"
	"strings"
	"syscall"
	"unsafe"
)

//Local devices

const  (
	guidBufLen = windows.MAX_PATH + 1
	volumeNameBufLen = windows.MAX_PATH + 1
	rootPathBufLen = windows.MAX_PATH + 1
	fileSystemBufLen = windows.MAX_PATH + 1
)

// 小驼峰方式 rootPathMountBufLen
// 参数都是必须是小驼峰  方法都是动词+大驼峰+名词
func getMountPoint(guidBuf []uint16) (mountPoint string,err error)  {
	var rootPathLen uint32
	//看到没有 都是这样的方式
	rootPathBuf := make([]uint16,rootPathBufLen)

	err = windows.GetVolumePathNamesForVolumeName(&guidBuf[0],&rootPathBuf[0],rootPathBufLen*2,&rootPathLen)
	if err != nil && err.(windows.Errno) == windows.ERROR_MORE_DATA {
		// Retry if buffer size is too small
		rootPathBuf = make([]uint16,(rootPathLen+1)/2)
		err = windows.GetVolumePathNamesForVolumeName(
			&guidBuf[0],&rootPathBuf[0],rootPathLen,&rootPathLen)
	}
	return windows.UTF16ToString(rootPathBuf),err
}
func getVolumeInfo(guidOrMountPointBuf []uint16)(volumeName string,fsType string,err error)  {
	volumeNameBuf := make([]uint16,volumeNameBufLen)
	fsTypeBuf := make([]uint16,fileSystemBufLen)
	err = windows.GetVolumeInformation(&guidOrMountPointBuf[0],&volumeNameBuf[0],volumeNameBufLen*2,
		nil,nil,nil,
		&fsTypeBuf[0],fileSystemBufLen*2)
	return windows.UTF16ToString(volumeNameBuf),windows.UTF16ToString(fsTypeBuf),err
}
//可以从 方法 参数 返回值 都是严格的 方法是 小腿欧风  参数都是 也是一样的方式
// 返回值初始化信息 totalBytes freeBytes err return
func getSpaceInfo(guidOrMountPointBuf []uint16) (totalBytes uint64,freeBytes uint64,err error) {
	err = windows.GetDiskFreeSpaceEx(&guidOrMountPointBuf[0],nil,&totalBytes,&freeBytes)
	return
}
//原子信息  var byutes vate tir toaize
// bufe = 如？+
// return
func getClusterInfo(guidOrMountPointBuf []uint16) (totalCluster uint32,clusterSize uint32,err error) {
	var sectorsPerCluster uint32
	var bytesPerSector uint32
	err = GetDiskFreeSpace(&guidOrMountPointBuf[0],&sectorsPerCluster,&bytesPerSector,nil,&totalCluster)
	clusterSize = bytesPerSector + sectorsPerCluster
	return
}
func getMount(guidOrMountPointBuf []uint16,isGUID bool)(m Mount,skip bool,warnings []string)  {
	var err error
	guidOrMountPoint := windows.UTF16ToString(guidOrMountPointBuf)

	mountPoint := guidOrMountPoint

	if isGUID {
		mountPoint, err = getMountPoint(guidOrMountPointBuf)
		if err != nil {
			warnings = append(warnings,fmt.Sprintf("%s: %s",guidOrMountPoint,err))
		}
		// Skip unmounted volumes
		if len(mountPoint) == 0 {
			skip = true
			return
		}
	}
	// Get volume name & filesystem type
	volumeName, fsType,err := getVolumeInfo(guidOrMountPointBuf)
	if err != nil{
		warnings = append(warnings,fmt.Sprintf("%s: %s",guidOrMountPoint,err))
	}

	//Get space info
	totalBytes, freeBytes,err := getSpaceInfo(guidOrMountPointBuf)
	if err != nil{
		warnings = append(warnings,fmt.Sprintf("%s: %s",guidOrMountPoint,err))
	}

	//Get cluster info
	totalClusters, clusterSize, err := getClusterInfo(guidOrMountPointBuf)
	if err != nil{
		warnings = append(warnings,fmt.Sprintf("%s: %s",guidOrMountPoint,err))
	}
	m = Mount{
		Device: volumeName,
		Mountpoint: mountPoint,
		FsType: fsType,
		Type: fsType,
		Opts: "",
		Total: totalBytes,
		Free: freeBytes,
		Used: totalBytes - freeBytes,
		Blocks: uint64(totalClusters),
		BlockSize: uint64(clusterSize),
	}
	m.DeviceType = deviceType(m)
	return
}
//最小单元组合的方式为 func 函数签名模式
//这样的方式 getMount getMiinfo getToamount getDispaspr cliinfo m kstop wairng []string
func getMountFromGUID(guidBuf []uint16)(m Mount,skip bool,warings []string)  {
	m, skip , warings = getMount(guidBuf,true)
	// Use GUID as volume name if no label was set
	if len(m.Device) == 0 {
		m.Device = windows.UTF16ToString(guidBuf)
	}
	return
}
func getMountFromMountPoint(mountPointBuf []uint16)(m Mount,warnings []string)  {
	m,_,warnings = getMount(mountPointBuf,false)
	// User mount point as volume name if no label was set
	if len(m.Device) == 0 {
		m.Device = windows.UTF16ToString(mountPointBuf)
	}
	return
}
func appendLocalMounts(mounts []Mount,warnins []string)([]Mount,[]string,error)  {
	guidBuf := make([]uint16,guidBufLen)

	hFindVolume, err := windows.FindFirstVolume(&guidBuf[0],guidBufLen*2)

	if err != nil {
		return mounts,warnins,err
	}
VloumeLoop:
	for ; ; err = windows.FindNextVolume(hFindVolume,&guidBuf[0],guidBufLen * 2) {
		if err != nil {
			switch err.(windows.Errno) {
			case windows.ERROR_NO_MORE_FILES:
				break VloumeLoop
			default:
				warnins = append(warnins,fmt.Sprintf("%s: %s",windows.UTF16ToString(guidBuf),err))
			}
		}
		if m, skip,w := getMountFromGUID(guidBuf); !skip {
			mounts =  append(mounts,m)
			warnins = append(warnins,w...)
		}
	}

	if err = windows.FindVolumeClose(hFindVolume); err != nil{
		warnins = append(warnins,fmt.Sprintf("%s",err))
	}
	return mounts,warnins,nil
}

//Network devices

func getMountFromNetResource(netResource NetResource) (m Mount,warings []string)  {
	mountPoint := windows.UTF16ToString(netResource.LocalName)
	if !strings.HasSuffix(mountPoint,string(filepath.Separator)) {
		mountPoint += string(filepath.Separator)
	}
	mountPointBuf := windows.StringToUTF16(mountPoint)

	m,_, warings = getMount(mountPointBuf,false)

	// Use remote name as volume name if no label was set
	if len(m.Device) == 0 {
		m.Device = windows.UTF16ToString(netResource.RemoteName)
	}
	return
}

// TDD 原则处理 BDD TDD BDD  remoteName localName
// appendNetworkMounts
//func appendNetworkMounts(mounts []Mount,warings []string) ([]Mount,[]string,error) {
//	hEnumResource, err := WNetOpenEnum(RESOURCE_CONNECTED)
//}

// Windows API
const (
	// Windows Networking const
	// Reference: https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/nf-winnetwk-wnetopenenumw
	RESOURCE_CONNECTED  = 0x00000001
	RESOURCE_GLOBALNET  = 0x00000002
	RESOURCE_REMEMBERED = 0x00000003
	RESOURCE_RECENT     = 0x00000004
	RESOURCE_CONTEXT    = 0x00000005

	RESOURCETYPE_ANY      = 0x00000000
	RESOURCETYPE_DISK     = 0x00000001
	RESOURCETYPE_PRINT    = 0x00000002
	RESOURCETYPE_RESERVED = 0x00000008
	RESOURCETYPE_UNKNOWN  = 0xFFFFFFFF

	RESOURCEUSAGE_CONNECTABLE   = 0x00000001
	RESOURCEUSAGE_CONTAINER     = 0x00000002
	RESOURCEUSAGE_NOLOCALDEVICE = 0x00000004
	RESOURCEUSAGE_SIBLING       = 0x00000008
	RESOURCEUSAGE_ATTACHED      = 0x00000010
	RESOURCEUSAGE_ALL           = RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED
	RESOURCEUSAGE_RESERVED      = 0x80000000
)

var (
	// Windows syscall
	modmpr      = windows.NewLazySystemDLL("mpr.dll")
	modkernel32 = windows.NewLazySystemDLL("kernel32.dll")

	procWNetOpenEnumW     = modmpr.NewProc("WNetOpenEnumW")
	procWNetCloseEnum     = modmpr.NewProc("WNetCloseEnum")
	procWNetEnumResourceW = modmpr.NewProc("WNetEnumResourceW")
	procGetDiskFreeSpaceW = modkernel32.NewProc("GetDiskFreeSpaceW")

	NetResourceSize = unsafe.Sizeof(NetResource{})
)
// Reference: https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/ns-winnetwk-netresourcew
type NetResource struct {
	Scope       uint32
	Type        uint32
	DisplayType uint32
	Usage       uint32
	LocalName   *uint16
	RemoteName  *uint16
	Comment     *uint16
	Provider    *uint16
}
//我们可以看出 m.
//简单类型 返回即可 否则返回 名称 必须写信息
func GetDiskFreeSpace(directoryName *uint16,sectorsPerCluster *uint32,bytesPerSector *uint32,numberOfClusters *uint32,totalNumberOfClusters *uint32)(err error)  {
	r1,_, e1 := syscall.Syscall6(procGetDiskFreeSpaceW.Addr(),5,uintptr(unsafe.Pointer(directoryName)),uintptr(unsafe.Pointer(sectorsPerCluster)),uintptr(unsafe.Pointer(bytesPerSector)),uintptr(unsafe.Pointer(numberOfClusters)),uintptr(unsafe.Pointer(totalNumberOfClusters)),0)
	if r1 == 0 {
		if e1 != 0 {
			err = e1
		}else {
			err = syscall.EINVAL
		}
	}
	return
}
